/* eslint-disable jsx-a11y/label-has-associated-control */
import { ActionFunctionArgs, LoaderFunctionArgs } from "@remix-run/node";
import { Form, useActionData, useFetcher, useLoaderData, useNavigation } from "@remix-run/react";
import { useEffect, useState, useRef } from "react";
import { useTranslation } from "react-i18next";
import { z } from "zod";
import { findAllRoles, RoleModel } from "~/.server/models/role";
import { countUsers, createUser, deleteUser, findUsers, updateUser, UserModel, zUser } from "~/.server/models/users";
import { Confirm } from "~/components/confirm";
import { FormFieldError } from "~/components/form_validate";
import useNotification, { Notification } from "~/components/notification";
import { Table } from "~/components/tree_table";
import i18nServer from "~/i18n/i18n.server";
import { PaginationProps, SortProps, TreeNode } from "~/types";
import { toTreeNode } from "~/utils";

// TODO 选择头像
export async function loader({ request }: LoaderFunctionArgs) {
  const { searchParams } = new URL(request.url);
  const action = searchParams.get("action");
  const locale = await i18nServer.getLocale(request);
  const isZh = locale === "zh";
  if (action == "tableData") {
    const currentPage = Number(searchParams.get("page") || 1);
    const pageSize = Number(searchParams.get("pageSize") || 20);
    const search = searchParams.get("search") || undefined;
    const column = searchParams.get("column") || undefined;
    const order = searchParams.get("order") || undefined;
    const users = await findUsers({ currentPage, pageSize, search, column, order });
    const usersCount = await countUsers({ search });
    return { users, usersCount, isZh };
  } else if (action == "roleData") {
    const roles = await findAllRoles();
    return { roles, isZh };
  }
  return { isZh };
}

export default function User() {
  const { t } = useTranslation();
  const actionData = useActionData<typeof action>();
  const { isZh } = useLoaderData<typeof loader>();

  // Notifications
  const { notifications, addNotification } = useNotification();
  useEffect(() => {
    if (actionData?.message) {
      addNotification({ type: actionData?.code === 0 ? "success" : "error", msg: actionData.message });
    }
    if (actionData?.code === 0) {
      closeUserDialog();
    }
  }, [actionData, addNotification]);

  // User table data
  const [userTableData, setUserTableData] = useState<UserModel[]>([]);
  const [userTableCount, setUserTableCount] = useState<number>(0);
  const userTableDataFetcher = useFetcher();
  const userTableDataFetcherUrl = "?action=tableData";
  useEffect(() => {
    if (userTableDataFetcher.state === "idle" && !userTableDataFetcher.data) {
      userTableDataFetcher.load(userTableDataFetcherUrl);
    }
    if (userTableDataFetcher.state === "idle" && userTableDataFetcher.data) {
      const data = userTableDataFetcher.data as { users?: UserModel[], usersCount?: number };
      if (data.users) {
        setUserTableData(data.users);
        setUserTableCount(data.usersCount ?? 0);
      }
    }
  }, [userTableDataFetcher]);
  const [pagination, setPagination] = useState<PaginationProps>({ page: 1, pageSize: 20 });
  const [sort, setSort] = useState<SortProps | null>(null);
  const onTableChange = (pagination: PaginationProps, sort: SortProps | null) => {
    setPagination(pagination);
    setSort(sort);
    reloadTableData(pagination, sort, search);
  };
  // search
  const [search, setSearch] = useState<string>("");
  const debounceTimeout = useRef<NodeJS.Timeout | null>(null);  // 防抖搜索
  const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
    const value = e.target.value;
    setSearch(value);

    if (debounceTimeout.current) {
      clearTimeout(debounceTimeout.current);
    }
    debounceTimeout.current = setTimeout(() => {
      reloadTableData(pagination, sort, value);
    }, 500);
  };
  const reloadTableData = (pageProps: PaginationProps = pagination, sortProps: SortProps | null = sort, searchValue: string = search) => {
    if (sortProps) {
      userTableDataFetcher.load(userTableDataFetcherUrl + `&search=${searchValue}&page=${pageProps.page}&pageSize=${pageProps.pageSize}&column=${sortProps.column}&order=${sortProps.order}`);
    } else {
      userTableDataFetcher.load(userTableDataFetcherUrl + `&search=${searchValue}&page=${pageProps.page}&pageSize=${pageProps.pageSize}`);
    }
  };

  // User dialog info
  const [userInfo, setUserInfo] = useState<Partial<UserModel>>({});
  const navigation = useNavigation();
  const isSubmitting = navigation.formAction === "/admin/users";

  const openUserDialog = () => {
    const dialog = document.getElementById("dialog-user") as HTMLDialogElement;
    dialog.showModal();
  };
  const closeUserDialog = () => {
    const dialog = document.getElementById("dialog-user") as HTMLDialogElement;
    dialog.close();
    setUserInfo({});
  };
  const handleFormChange = (event: React.ChangeEvent<HTMLInputElement> | React.ChangeEvent<HTMLSelectElement>) => {
    const { name, value } = event.target;
    setUserInfo({ ...userInfo, [name]: value });
  };
  // render roles checkbox which in user dialog
  const [roleData, setRoleData] = useState<RoleModel[]>([]);
  const roleDataFetcher = useFetcher();
  const roleDataFetcherUrl = "?action=roleData";
  useEffect(() => {
    if (roleDataFetcher.state === "idle" && !roleDataFetcher.data) {
      roleDataFetcher.load(roleDataFetcherUrl);
    }
    if (roleDataFetcher.state === "idle" && roleDataFetcher.data) {
      const data = roleDataFetcher.data as { roles?: RoleModel[] };
      if (data.roles) {
        setRoleData(data.roles);
      }
    }
  }, [roleDataFetcher]);
  const renderRoles = () => {
    return roleData.map((role) => (
      <li key={role.id} className="flex items-center">
        <label className="cursor-pointer label gap-1">
          <input
            type="checkbox"
            className="checkbox checkbox-primary"
            name="roleIds"
            value={role.id}
            checked={(userInfo.roles ?? []).some(r => r.id === role.id)}
            onChange={(e) => handleRoleCheck(e, role.id!)}
          />
          <span className="label-text">{isZh ? role.zh_name ?? role.name : role.name}</span>
        </label>
      </li>
    ));
  };
  const handleRoleCheck = (e: React.ChangeEvent<HTMLInputElement>, roleId: number) => {
    const checked = e.target.checked;
    let roles = [...(userInfo.roles || [])];
    if (checked) {
      roles.push({ id: roleId, name: "" });
    } else {
      roles = roles.filter(role => role.id !== roleId);
    }
    setUserInfo({ ...userInfo, roles: roles });
  }
  // Add User
  const addUser = () => {
    openUserDialog();
  };
  const editUser = (id: number) => {
    setUserInfo(userTableData.find((user) => user.id === id) || {});
    openUserDialog();
  };
  // Delete User
  const [isDeleting, setIsDeleting] = useState<boolean>(false);
  const [deleteUserId, setDeleteUserId] = useState<number | null>(null);
  const deleteUser = (id: number) => {
    setIsDeleting(true);
    setDeleteUserId(id);
  };
  const deleteUserFetcher = useFetcher<typeof action>();
  const confirmDeleteRole = () => {
    deleteUserFetcher.submit({ id: deleteUserId, action: "delete" }, { method: "post" });
    if (deleteUserFetcher.data?.code === 0) {
      addNotification({ type: "info", msg: deleteUserFetcher.data.message });
    } else if (deleteUserFetcher.data?.message) {
      addNotification({ type: "warning", msg: deleteUserFetcher.data?.message });
    }
    setIsDeleting(false);
    setDeleteUserId(null);
  };
  const cancelDeleteRole = () => {
    setIsDeleting(false);
    setDeleteUserId(null);
  };

  return (
    <>
      <div className="card bg-base-100 shadow-xl">
        <div className="card-body">
          <div className="card-title justify-between">
            <input type="text" placeholder="Search" value={search} onChange={handleSearch} className="input input-bordered w-full max-w-xs" />
            <button className="btn btn-primary" onClick={addUser}>{t("add") + t("space") + t("user.name")}</button>
          </div>
          <div className="overflow-x-auto">
            <Table
              data={toTreeNode(userTableData)}
              dataCount={userTableCount}
              onTableChange={onTableChange}
              isLoading={userTableDataFetcher.state !== "idle"}
              columns={[
                {
                  id: "id",
                  name: t("id"),
                  sortable: true,
                },
                {
                  id: "avatar",
                  name: t("avatar"),
                },
                {
                  id: "username",
                  name: t("username"),
                  sortable: true,
                },
                {
                  id: "nickname",
                  name: t("nickname"),
                  sortable: true,
                },
                {
                  id: "email",
                  name: t("email"),
                  sortable: true,
                },
                {
                  id: "mobile",
                  name: t("mobile"),
                  sortable: true,
                },
                {
                  id: "role",
                  name: t("role.name"),
                  render: (node: TreeNode) => (
                    <div>
                      {node.roles?.map((r: RoleModel) =>
                        isZh ? r.zh_name ?? r.name : r.name
                      ).join(", ")}
                    </div>
                  ),
                },
                {
                  id: "action",
                  name: t("action"),
                  className: "min-w-20",
                  render: (node: TreeNode) => (
                    <>
                      <button className="btn btn-ghost btn-circle btn-sm" onClick={() => editUser(Number(node.id))}>
                        <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="size-5">
                          <path strokeLinecap="round" strokeLinejoin="round" d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L10.582 16.07a4.5 4.5 0 0 1-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 0 1 1.13-1.897l8.932-8.931Zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0 1 15.75 21H5.25A2.25 2.25 0 0 1 3 18.75V8.25A2.25 2.25 0 0 1 5.25 6H10" />
                        </svg>
                      </button>
                      <button className="btn btn-ghost btn-circle btn-sm" onClick={() => deleteUser(Number(node.id))}>
                        <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="size-5 text-error">
                          <path strokeLinecap="round" strokeLinejoin="round" d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0" />
                        </svg>
                      </button>
                    </>
                  ),
                }
              ]}
            />
          </div>
        </div>
      </div>

      {/* 添加或修改 */}
      <dialog id="dialog-user" className="modal" >
        <div className="modal-box max-w-3xl">
          <h1 className="font-bold text-2xl text-center">{(userInfo.id || 0 > 0 ? t("edit") : t("add")) + t("space") + t("user.name")}</h1>
          <Form method="post">
            <input type="hidden" name="id" defaultValue={userInfo.id ?? 0} />
            <div className="grid grid-cols-1 sm:grid-cols-2 gap-x-5">
              <label className="form-control w-full">
                <div className="label">
                  <span className="label-text after:content-['*'] after:ml-0.5 after:text-error">{t("username")}</span>
                </div>
                <input type="text" name="username"
                  value={userInfo?.username ?? ""}
                  onChange={handleFormChange} placeholder={t("username")}
                  className={`input input-bordered w-full ${actionData?.username && "input-error"}`} />
                <FormFieldError msg={actionData?.username} />
              </label>
              <label className="form-control w-full">
                <div className="label">
                  <span className="label-text after:content-['*'] after:ml-0.5 after:text-error">{t("password")}</span>
                </div>
                <input type="text" name="password"
                  value={userInfo?.password ?? ""}
                  onChange={handleFormChange} placeholder={t("password")}
                  className={`input input-bordered w-full ${actionData?.password && "input-error"}`} />
                <FormFieldError msg={actionData?.password} />
              </label>
              <label className="form-control w-full">
                <div className="label">
                  <span className="label-text">{t("nickname")}</span>
                </div>
                <input type="text" name="nickname"
                  value={userInfo?.nickname ?? ""}
                  onChange={handleFormChange}
                  placeholder={t("nickname")}
                  className="input input-bordered w-full" />
              </label>
              <label className="form-control w-full">
                <div className="label">
                  <span className="label-text">{t("email")}</span>
                </div>
                <input type="text" name="email"
                  value={userInfo?.email ?? ""}
                  onChange={handleFormChange}
                  placeholder={t("email")}
                  className={`input input-bordered w-full ${actionData?.email && "input-error"}`} />
                <FormFieldError msg={actionData?.email} />
              </label>
              <label className="form-control w-full">
                <div className="label">
                  <span className="label-text">{t("mobile")}</span>
                </div>
                <input type="text"
                  name="mobile"
                  value={userInfo?.mobile ?? ""}
                  onChange={handleFormChange}
                  placeholder={t("mobile")}
                  className="input input-bordered w-full" />
              </label>
              <label className="form-control w-full">
                <div className="label">
                  <span className="label-text">{t("avatar")}</span>
                </div>
                <input type="text"
                  name="avatar"
                  value={userInfo?.avatar ?? ""}
                  onChange={handleFormChange}
                  placeholder={t("avatar")}
                  className="input input-bordered w-full" />
              </label>
              <div className="form-control w-full sm:col-span-2">
                <div className="label">
                  <span className="label-text">{t("role.name")}</span>
                </div>
                <ul className="flex flex-wrap gap-2">
                  {renderRoles()}
                </ul>
              </div>
              <div className="modal-action sm:col-span-2">
                <button className={`btn btn-primary ${isSubmitting ? "btn-disabled" : ""}`}>
                  {isSubmitting ? <span className="loading loading-spinner"></span> : ""}
                  {t("save")}
                </button>
                <button className="btn" onClick={(e) => { e.preventDefault(); closeUserDialog(); }}>{t("close")}</button>
              </div>
            </div>
          </Form>
        </div>
      </dialog>
      <Notification notifications={notifications} />
      {isDeleting
        ? <Confirm
          isSubmitting={deleteUserFetcher.state !== "idle"}
          message={t("confirmDelete") + " [" + userTableData.find(u => u.id === deleteUserId)?.username + "]"}
          onConfirm={confirmDeleteRole}
          onCancel={cancelDeleteRole} />
        : ""}
    </>
  )
}

export async function action({ request }: ActionFunctionArgs) {
  const t = await i18nServer.getFixedT(request);

  const body = await request.formData();
  if (body.get("action") === "delete") {
    const r = await deleteUser(Number(body.get("id")));
    return {
      code: 0,
      message: r ? t("delete") + t("space") + t("success") : t("has_children"),
    }
  }

  const validate = zUser.extend({
    username: z.string().min(1, { message: `${t("username")} ${t("required")}` }),
    password: z.string().min(1, { message: `${t("password")} ${t("required")}` }),
    email: z.string().email({ message: `${t("email_error")}` }).optional(),
  })
  const data = Object.fromEntries(body);
  const result = validate.safeParse(data);
  if (!result.success) {
    const error = result.error.flatten();
    console.log(error.fieldErrors);
    return {
      ...error.fieldErrors,
      code: 1,
      message: t("something_wrong"),
    }
  }

  const roles = body.getAll("roleIds").map(roleId => ({ id: Number(roleId), name: "" }));
  const model = { ...result.data, roles };
  model.id ? await updateUser(model) : await createUser(model);
  return {
    code: 0,
    message: (model.id ? t("edit") : t("add")) + t("space") + t("success"),
  }
}